package org.KeyCrafter.key;

import org.KeyCrafter.cmd.SetCommand;
import org.KeyCrafter.obj.KeyCrafterObjFactory;
import org.KeyCrafter.obj.KeyCrafterObject;
import org.KeyCrafter.obj.SetObj;
import org.KeyCrafter.obj.type.SetType;
import org.KeyCrafter.server.session.ClientSession;
import org.KeyCrafter.util.SharedReply;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import static org.KeyCrafter.obj.KeyCrafterObject.KeyCrafter_SET;
import static org.KeyCrafter.util.ReplyUtil.*;

public class SetKey implements SetCommand {
    @Override
    public void saddCommand(ClientSession c) {
        String key = c.argv(1);
        // 取出集合对象
        KeyCrafterObject set = c.db().lookupKeyWrite(key);
        // 对象不存在，创建一个新的，并将它关联到数据库
        if (set == null) {
            set = KeyCrafterObjFactory.newSetObj();
            c.db().dbAdd(key, set);
        } else {
            // 对象存在，检查类型
            if (!(set instanceof SetObj)) {
                addReply(c, SharedReply.wrongTypeErr);
                return;
            }
        }
        // 将所有输入元素添加到集合中
        int n = c.argc();
        int added = 0;
        SetType setType = ((SetObj) set).getValue();
        for (int j = 2; j < n; j++) {
            // 只有元素未存在于集合时，才算一次成功添加
            if (setType.add(c.argv(j))) {
                added++;
            }
        }
        // 将数据库设为脏
        c.db().addDirty(added);
        // 返回添加元素的数量
        addReplyLong(c, added);
    }

    /**
     * 基本逻辑：
     *  先将集合按基数大小排序，以一个集合(基数最小)为标准，遍历该集合中所有元素，依次判断该元素是否在其余所有集合中。
     *  如果是加入结果集合
     *  如果不是则舍弃该元素
     * @param c
     */
    @Override
    public void sinterCommand(ClientSession c) {
        int argc = c.argc();
        String[] setKeys = new String[argc - 1];
        for (int i = 0; i < setKeys.length; i++) {
            setKeys[i] = c.argv(i + 1);
        }
        sinterGenericCommand(c, setKeys, null);
    }

    @Override
    public void smembersCommand(ClientSession c) {
        sinterCommand(c);
    }

    @Override
    public void sinterstoreCommand(ClientSession c) {
        int argc = c.argc();
        String[] setKeys = new String[argc - 2];
        for (int i = 0; i < setKeys.length; i++) {
            setKeys[i] = c.argv(i + 2);
        }
        sinterGenericCommand(c, setKeys, c.argv(1));
    }

    private static void sinterGenericCommand(ClientSession c, String[] setKeys, String destKey) {
        // 所有带计算的集合数组
        SetObj[] sets = new SetObj[setKeys.length];
        for (int j = 0; j < setKeys.length; j++) {
            // 取出对象
            // 第一次执行时，取出的是 dest 集合
            // 之后执行时，取出的都是 source 集合
            KeyCrafterObject setObj = destKey == null ?
                    c.db().lookupKeyWrite(setKeys[j]) :
                    c.db().lookupKeyRead(setKeys[j]);
            // 对象不存在，放弃执行，进行清理
            if (setObj == null) {
                if (destKey != null) {
                    if (c.db().delete(destKey)) {
                        c.db().incrDirty();
                    }
                    addReply(c, SharedReply.cZero);
                } else {
                    addReply(c, SharedReply.emptyMultiBulk);
                }
                return;
            }
            // 检查对象的类型
            if (!checkType(c, setObj, KeyCrafter_SET)) {
                // 类型出错，返回
                return;
            }
            // 将集合对象添加到数组
            sets[j] = (SetObj) setObj;
        }
        // 按基数对集合进行排序，这样提升算法的效率
        Arrays.sort(sets, SetKey::qSortCompareSetsByCardinality);
        // 使用一个 List，将之后的回复都添加到 List 中
        List<String> reply = null;
        SetObj detSet = null;
        if (destKey == null) {
            reply = new LinkedList<>();
        } else {
            // 有目标 key，创建目标集合
            detSet = KeyCrafterObjFactory.newSetObj();
        }
        // 遍历基数最小的第一个集合（取交集，结果是每个集合的子集，所以以最小集合
        // 为基准，查看其元素是否在其他集合中）
        // 并将它的元素和所有其他集合进行对比
        // 如果有至少一个集合不包含这个元素，那么这个元素不属于交集
        Iterator si = sets[0].getValue().initIterator();
        Object eleObj;
        while ((eleObj = si.next()) != null) {
            // 遍历其他集合，检查元素是否在这些集合中存在
            int j;
            for (j = 1; j < sets.length; j++) {
                // 在其他集合中查找这个对象是否存在
                if (!sets[j].getValue().isMember(eleObj)) {
                    break;
                }
            }
            // 如果所有集合都带有目标元素的话，那么执行以下代码
            if (j == sets.length) {
                // SINTER 命令，直接返回结果集元素
                if (destKey == null) {
                    //addReplyBulk(c, eleObj.toString());
                    reply.add(eleObj.toString());
                } else {
                    // SINTERSTORE 命令，将结果添加到结果集中
                    detSet.getValue().add(eleObj);
                }
            }
        }
        // SINTERSTORE 命令，将结果集关联到数据库
        if (destKey != null) {
            // 删除现在可能有的 dstkey
            boolean deleted = c.db().delete(destKey);
            // 如果结果集非空，那么将它关联到数据库中
            if (detSet.getValue().size() > 0) {
                c.db().dbAdd(destKey, detSet);
                addReplyLong(c, detSet.getValue().size());
            } else {
                addReply(c, SharedReply.cZero);
            }
            c.db().incrDirty();
        } else {
            // SINTER 命令，回复结果集的基数
            addReplyArray(c, reply);
        }
    }

    /*
     * 计算集合 s1 的基数减去集合 s2 的基数之差
     */
    private static int qSortCompareSetsByCardinality(SetObj s1, SetObj s2) {
        long l1 = s1.getValue().size();
        long l2 = s2.getValue().size();
        return Long.compare(l1, l2);
    }
}
